﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using ArtMath.Core.Helper;
using ArtMath.Setting;

namespace ArtMath.Core.Data
{
    public enum RectangularCoordinateLocation
    {
        Quadrant1,
        Quadrant2,
        Quadrant3,
        Quadrant4,
        PositiveX,
        NegativeX,
        PositiveY,
        NegativeY,
        Original,
    }
    /// <summary>
    /// 表示在二维平面中定义点的双精度浮点 x 和 y 坐标的有序对。
    /// </summary>
    [Serializable]
    public struct PointD
    {
        public const double PaintCritical = 1e8;
        /// <summary>
        /// 表示 PointD 类的、成员数据未被初始化的新实例。
        /// </summary>
        public static readonly PointD Empty = new PointD(0, 0);
        /// <summary>
        /// 获取或设置此 PointD 的 x 坐标。
        /// </summary>
        public double X { get; set; }
        /// <summary>
        /// 获取或设置此 PointD 的 y 坐标。
        /// </summary>
        public double Y { get; set; }
        /// <summary>
        /// 用指定坐标初始化 PointD 类的新实例。
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public PointD(double x, double y)
        {
            X = x;
            Y = y;
        }
        public bool IsEmpty
        {
            get { return X == 0 && Y == 0; }
        }
        /// <summary>
        /// 获取点在平面直角坐标系中的位置
        /// </summary>
        public RectangularCoordinateLocation RCLocation
        {
            get
            {
                if (X > 0)
                {
                    if (Y > 0)
                    {
                        return RectangularCoordinateLocation.Quadrant1;
                    }
                    else if (Y == 0)
                    {
                        return RectangularCoordinateLocation.PositiveX;
                    }
                    else
                    {
                        return RectangularCoordinateLocation.Quadrant4;
                    }
                }
                else if (X == 0)
                {
                    if (Y > 0)
                    {
                        return RectangularCoordinateLocation.PositiveY;
                    }
                    else if (Y == 0)
                    {
                        return RectangularCoordinateLocation.Original;
                    }
                    else
                    {
                        return RectangularCoordinateLocation.NegativeY;
                    }
                }
                else
                {
                    if (Y > 0)
                    {
                        return RectangularCoordinateLocation.Quadrant2;
                    }
                    else if (Y == 0)
                    {
                        return RectangularCoordinateLocation.NegativeX;
                    }
                    else
                    {
                        return RectangularCoordinateLocation.Quadrant3;
                    }
                }
            }
        }
        public static PointD FromPoint(Point pt)
        {
            return new PointD(pt.X, pt.Y);
        }
        public static PointD FromPointF(PointF pt)
        {
            return new PointD(pt.X, pt.Y);
        }
        public static PointD FromString(string str)
        {
            string[] tokens = str.Split(',', '，');
            if (tokens.Length == 2)
            {
                try
                {
                    return new PointD(double.Parse(tokens[0]), double.Parse(tokens[1]));
                }
                catch (Exception)
                {
                }
            }
            return Empty;
        }
        /// <summary>
        /// 计算该点与另一个点的距离
        /// </summary>
        /// <param name="point">另一个点坐标</param>
        /// <returns></returns>
        public double DistanceTo(PointD point)
        {
            return Utility.TwoPointsDistance(X, Y, point.X, point.Y);
        }
        /// <summary>
        /// 计算该点到一条直线的距离
        /// </summary>
        /// <param name="A">直线x系数</param>
        /// <param name="B">直线y系数</param>
        /// <param name="C">直线常数项</param>
        /// <returns></returns>
        public double DistanceTo(double A, double B, double C)
        {
            return Math.Abs(A * X + B * Y + C) / Math.Sqrt(A * A + B * B);
        }
        public double DistanceTo(PointD pt1, PointD pt2)
        {
            double[] abc = pt1.GetABC(pt2);
            return DistanceTo(abc[0], abc[1], abc[2]);
        }
        public double DistanceTo(PointD location, double slope)
        {
            double[] abc = location.GetABC(slope);
            return DistanceTo(abc[0], abc[1], abc[2]);
        }
        public double[] GetABC(PointD pt)
        {
            double[] res = new double[3];
            if (X == pt.X)
            {
                res[0] = 1;
                res[1] = 0;
                res[2] = -X;
            }
            else
            {
                res[0] = pt.Y - Y;
                res[1] = X - pt.X;
                res[2] = -res[1] * Y - res[0] * X;
            }
            return res;
        }
        public double[] GetABC(double slope)
        {
            double[] res = new double[3];
            if (double.IsInfinity(slope))
            {
                res[0] = 1;
                res[1] = 0;
                res[2] = -X;
            }
            else
            {
                res[0] = slope;
                res[1] = -1;
                res[2] = Y - slope * X;
            }
            return res;
        }
        /// <summary>
        /// 求该点关于某条直线的对称点
        /// </summary>
        /// <param name="A">直线x系数</param>
        /// <param name="B">直线y系数</param>
        /// <param name="C">直线常数项</param>
        /// <returns></returns>
        public PointD GetMirrorPoint(double A, double B, double C)
        {
            double tmp = (A * X + B * Y + C) / (A * A + B * B);
            return new PointD(X - 2 * A * tmp, Y - 2 * B * tmp);
        }
        /// <summary>
        /// 求该点关于某条直线的对称点
        /// </summary>
        /// <param name="pointOnAxis">直线上的点</param>
        /// <param name="KofAxis">直线斜率</param>
        /// <returns></returns>
        public PointD GetMirrorPoint(PointD pointOnAxis, double KofAxis)
        {
            if (double.IsInfinity(KofAxis))
            {
                return GetMirrorPoint(1, 0, -pointOnAxis.X);
            }
            else
            {
                return GetMirrorPoint(KofAxis, -1, pointOnAxis.Y - KofAxis * pointOnAxis.X);
            }
        }
        /// <summary>
        /// 求该点绕某点旋转一定角度后的点坐标
        /// </summary>
        /// <param name="center">旋转中心</param>
        /// <param name="angle">旋转角度</param>
        /// <returns></returns>
        public PointD RotateAt(PointD center, double angle)
        {
            double sin = Math.Sin(angle), cos = Math.Cos(angle), deltaX = X - center.X, deltaY = Y - center.Y;
            return new PointD(deltaX * cos - deltaY * sin, deltaX * sin + deltaY * cos) + center;
        }
        /// <summary>
        /// 求该点绕原点（0,0）旋转一定角度后的坐标
        /// </summary>
        /// <param name="angle">旋转角度</param>
        /// <returns></returns>
        public PointD Rotate(double angle)
        {
            return RotateAt(new PointD(0, 0), angle);
        }
        /// <summary>
        /// 求该点沿某一方向移动一定距离后的点坐标
        /// </summary>
        /// <param name="angle">方向角（弧度）</param>
        /// <param name="length">距离</param>
        /// <returns></returns>
        public PointD Offset(double angle, double length)
        {
            return this + length * Utility.GetUnitVector(angle);
            //return new PointD(X + length * Math.Cos(angle), Y - length * Math.Sin(angle));
        }
        public PointD POffset(double angle, double length)
        {
            return new PointD(X + length * Math.Cos(angle), Y - length * Math.Sin(angle));
        }
        public PointD BeforeOffset(double angle, double length)
        {
            return this - length * Utility.GetUnitVector(angle);
            //return new PointD(X - length * Math.Cos(angle), Y - length * Math.Sin(angle));
        }
        /// <summary>
        /// 判断该点是否在某一封闭多边形内
        /// </summary>
        /// <param name="region">封闭多边形顶点坐标</param>
        /// <param name="containsEdge">是否包含边界</param>
        /// <returns></returns>
        public bool InRegion(IList<PointD> region, bool containsEdge = true)
        {
            int sgn = Math.Sign(Utility.CrossProduct(this, region[0], this, region[1]));
            if (sgn == 0 && !containsEdge) return false;
            for (int i = 1; i < region.Count; i++)
            {
                int tmp = Math.Sign(Utility.CrossProduct(this, region[i], this, region[i == region.Count - 1 ? 0 : i + 1]));
                if (tmp != sgn || tmp == 0 && !containsEdge)
                {
                    return false;
                }
            }
            return true;
        }
        /// <summary>
        /// 计算从该点出发到指定点的方向角（范围：[0,2π)，单位：弧度）
        /// </summary>
        /// <param name="point">终点坐标</param>
        /// <returns></returns>
        public double GetAngle(PointD point)
        {
            double tmp = Math.Atan2(point.Y - Y, point.X - X);
            if (tmp < 0) tmp += Angle.RadRound;
            return tmp;
        }
        public double GetAngle(double A, double B, double C)
        {
            return GetAngle(GetProjectivePoint(A, B, C));
        }
        /// <summary>
        /// 求该点到某直线的投影点坐标
        /// </summary>
        /// <param name="A">直线方程x系数</param>
        /// <param name="B">直线方程y系数</param>
        /// <param name="C">直线方程常数项</param>
        /// <returns></returns>
        public PointD GetProjectivePoint(double A, double B, double C)
        {
            return Utility.GetCrossPoint(A, B, -C, B, -A, B * X - A * Y);
        }
        public PointD GetProjectivePoint(PointD pt1, PointD pt2)
        {
            double[] abc = pt1.GetABC(pt2);
            return GetProjectivePoint(abc[0], abc[1], abc[2]);
        }
        public PointD GetProjectivePoint(PointD lineLocation, double slope)
        {
            if (double.IsInfinity(slope))
            {
                return GetProjectivePoint(1, 0, -lineLocation.X);
            }
            else
            {
                return GetProjectivePoint(slope, -1, lineLocation.Y - slope * lineLocation.X);
            }
        }
        /// <summary>
        /// 判断该点是否在由2点组成的线段上
        /// </summary>
        /// <param name="pt1">线段端点1</param>
        /// <param name="pt2">线段端点2</param>
        /// <returns></returns>
        public bool OnLineSegment(PointD pt1, PointD pt2)
        {
            if (Utility.OnOneLine(pt1, pt2, this))
            {
                double minX = Math.Min(pt1.X, pt2.X), maxX = Math.Max(pt1.X, pt2.X), 
                    minY = Math.Min(pt1.Y, pt2.Y), maxY = Math.Max(pt1.Y, pt2.Y);
                if (X >= minX && X <= maxX && Y >= minY && Y <= maxY)
                {
                    return true;
                }
            }
            return false;
        }
        public int WhereStraightLine(PointD location, double k)
        {
            if (double.IsNegativeInfinity(k)) k = double.PositiveInfinity;
            double cj = Utility.CrossProduct(Utility.GetUnitVector(Utility.GetSlopeAngle(k)), this - location);
            if (cj.AlmostEqual(0)) return 0;
            return Math.Sign(cj) * (k >= 0 ? 1 : -1);
        }
        public PointD GetNearestPoint(IList<PointD> pts)
        {
            if (pts.Count == 1)
            {
                return pts[0];
            }
            double minDist = double.PositiveInfinity;
            int minIndex = -1;
            for (int i = 0; i < pts.Count; i++)
            {
                double dist = DistanceTo(pts[i]);
                if (dist < minDist)
                {
                    minDist = dist;
                    minIndex = i;
                }
            }
            return pts[minIndex];
        }
        //public PointD FixOnEllipse(PointD center, double a, double b, double angle)
        //{
        //    double theta = center.GetAngle(this) - angle;
        //    return center + new PointD(a * Math.Cos(theta), b * Math.Sin(theta)).Rotate(angle);
        //}
        public PointD? GetNearestPointOnBrokenLine(IList<PointD> lineSamples, bool isClosed = false)
        {
            if (lineSamples == null || lineSamples.Count == 0) return null;
            double minDist = double.PositiveInfinity;
            int index = 0, length = lineSamples.Count;
            if (!isClosed) length--;
            bool isTerminal = true;
            for (int l = 0; l < length; l++)
            {
                int r = l < lineSamples.Count - 1 ? l + 1 : 0;
                double dist;
                if (Utility.ScalarProduct(lineSamples[l], lineSamples[r], lineSamples[l], this) > 0)
                {
                    if (Utility.ScalarProduct(lineSamples[r], lineSamples[l], lineSamples[r], this) > 0)
                    {
                        dist = DistanceTo(lineSamples[l], lineSamples[r]);
                        if (dist < minDist)
                        {
                            minDist = dist;
                            index = l;
                            isTerminal = false;
                        }
                    }
                    else
                    {
                        dist = DistanceTo(lineSamples[r]);
                        if (dist < minDist)
                        {
                            minDist = dist;
                            index = r;
                            isTerminal = true;
                        }
                    }
                }
                else
                {
                    dist = DistanceTo(lineSamples[l]);
                    if (dist < minDist)
                    {
                        minDist = dist;
                        index = l;
                        isTerminal = true;
                    }
                }
            }
            if (isTerminal)
            {
                return lineSamples[index];
            }
            else
            {
                return GetProjectivePoint(lineSamples[index], lineSamples[index < lineSamples.Count - 1 ? index + 1 : 0]);
            }
        }
        public double GetSlope(PointD pt)
        {
            return (Y - pt.Y) / (X - pt.X);
        }
        public double GetRate(PointD pt1, PointD pt2)
        {
            return DistanceTo(pt1) / DistanceTo(pt2);
        }
        public bool AlmostEquals(PointD pt, bool autoFix = true)
        {
            return AlmostEquals(pt.X, pt.Y, autoFix);
        }
        public bool AlmostEquals(double x, double y, bool autoFix = true)
        {
            if (Utility.TwoPointsDistance(X, Y, x, y) <= Settings.Instance.PointEPS)
            {
                if (autoFix)
                {
                    X += (x - X) / 2;
                    Y += (y - Y) / 2;
                }
                return true;
            }
            return false;
        }
        public bool IsNumberPoint
        {
            get { return X.IsNumber() && Y.IsNumber(); }
        }
        public bool IsPaintable
        {
            get
            {
                return IsNumberPoint && X >= -PaintCritical && X <= PaintCritical && Y >= -PaintCritical && Y <= PaintCritical;
            }
        }
        public Point ToPoint()
        {
            return new Point((int)X, (int)Y);
        }
        public PointF ToPointF()
        {
            return new PointF((float)X, (float)Y);
        }
        public static bool operator ==(PointD left, PointD right)
        {
            return left.X == right.X && left.Y == right.Y;
        }
        public static bool operator !=(PointD left, PointD right)
        {
            return !(left == right);
        }
        public override bool Equals(object obj)
        {
            if (obj is PointD)
            {
                PointD pt = (PointD)obj;
                return this == pt;
            }
            return false;
        }
        public override int GetHashCode()
        {
            long bits = BitConverter.DoubleToInt64Bits(X) ^ (BitConverter.DoubleToInt64Bits(Y) * 31);
            return (int)bits ^ (int)(bits >> 32);
        }
        public static PointD operator +(PointD left, PointD right)
        {
            return new PointD(left.X + right.X, left.Y + right.Y);
        }
        public static PointD operator +(PointD init, SizeD offset)
        {
            return new PointD(init.X + offset.Width, init.Y + offset.Height);
        }
        public static PointD operator -(PointD left, PointD right)
        {
            return new PointD(left.X - right.X, left.Y - right.Y);
        }
        public static PointD operator -(PointD init, SizeD offset)
        {
            return new PointD(init.X - offset.Width, init.Y - offset.Height);
        }
        public static PointD operator -(PointD pt)
        {
            return new PointD(-pt.X, -pt.Y);
        }
        public static PointD operator *(PointD left, PointD right)
        {
            return new PointD(left.X * right.X, left.Y * right.Y);
        }
        public static PointD operator *(double lambda, PointD pt)
        {
            return new PointD(lambda * pt.X, lambda * pt.Y);
        }
        public static PointD operator *(PointD pt, double lambda)
        {
            return lambda * pt;
        }
        public static PointD operator /(PointD left, PointD right)
        {
            return new PointD(left.X / right.X, left.Y / right.Y);
        }
        public static PointD operator /(PointD left, double right)
        {
            return new PointD(left.X / right, left.Y / right);
        }
        public static bool operator >(PointD left, PointD right)
        {
            return left.X > right.X && left.Y > right.Y;
        }
        public static bool operator <(PointD left, PointD right)
        {
            return left.X < right.X && left.Y < right.Y;
        }
        public static bool operator >=(PointD left, PointD right)
        {
            return left > right || left == right;
        }
        public static bool operator <=(PointD left, PointD right)
        {
            return left < right || left == right;
        }
        public override string ToString()
        {
            return $"({X}, {Y})";
        }
    }
}
